An application programming interface (“API”) is a computer process or mechanism that allows other processes to work together. In the familiar setting of a personal computer running an operating system and various applications such as MICROSOFT WORD® and ADOBE ACROBAT READER®, the API allows applications to communicate with the operating system. An application makes calls to the operating system API to invoke operating system services. The actual code behind the operating system API is located in a collection of dynamic link libraries (“DLLs”).
Database software can also provide an API. A database API allows processes to make calls to a database to invoke database services. Modern database software such as MICROSOFT SQL SERVER®, IBM DB2®, ORACLE DATABASE®, and SYBASE IQ®, all provides APIs. The vast majority of enterprise applications today use databases, and therefore also use the APIs that allow applications to access and manipulate those databases. It is desirable to provide these applications, whether client-side, middle-tier, or server-side applications, with the most responsive and scalable database API possible. This is especially true for data access-intensive applications.
While databases, operating systems, and other applications may provide APIs, there are fundamental differences between a database API and an operating system API. One such difference stems from the fact that an application such as a database typically does not have direct access and control over a system's hardware resources, while an operating system does have such control. A database is a complex application that is supported by the platform of an operating system. Therefore a function call to a database API must be implemented by database software via one or more subsequent function calls to an operating system API—the operating system platform on which the database sits.
The traditional setting for databases versus operating systems may also play a role in the differing course of development for database and operating system APIs. Databases are less prevalent than operating systems, and therefore databases may have been subjected to less commercial development expertise. While operating systems are both local as well as remote, in terms of residing on both client and server devices, databases are more often remote, because they contain a large and valuable supply of data—often too large to be stored locally, but that may be made accessible to multiple client devices. Remote access involves extra complexity in accessing services, because all communications must be properly packaged for remote delivery, and unpackaged at the remote location.
Another difference between database APIs and operating system APIs is the nature of the typical request to such APIs. The services provided by databases are different than those provided by an operating system. For example, a database may be requested to retrieve data that fits a particular description, it may be requested to join data with other data and so on. If a thread requests data from a database, it may be likely that a user has requested the data and is not interested in performing other operations without the requested data.
An operating system is responsible for initiating processes, and for managing memory for processes. Any process running on an operating system may comprise multiple threads. An operating system may be requested to create a new process handle, to exit a current process, to open an existing process, or to terminate an existing process. It may also be requested to create a file, to read a file, to read a buffer from a file into memory, to write a buffer from memory to disk, or to close a handle associated with a kernel object.
Perhaps because of the above differences between database and operating system APIs, database developers have not implemented both synchronous and asynchronous APIs, while operating systems developers have. A synchronous API is one in which a calling thread waits on an operation to complete before proceeding with other activity. An asynchronous API allows a calling thread to initiate an operation, and then continue executing without waiting on the operation to complete or performing some other operation to check on the progress of a database operation. Asynchronous operating system APIs allow threads to call functions such as reading a buffer asynchronously from a file into memory, writing a buffer asynchronously from memory to a disk, and so on. This allows an application to request a save of a document, for example, and move on to other operations without waiting for the document to save to disk.
In contrast, database APIs have traditionally been synchronous. However, with the growth of network computing, the corresponding storage of more and larger data objects in databases, the increase in server-based processes that may perform many operations simultaneously for many different users, and the increased complexity and power of applications requesting database services, this traditional approach may no longer be appropriate.
A traditional synchronous database API is illustrated in FIG. 1. The three steps of FIG. 1 summarize the procedure for obtaining data or other services from a database. In a synchronous arrangement, a process thread 100 first submits a call to a database API 101. The call invokes a requested function of a database 102. For example, the process thread 100 may have requested that the database 102 open a connection or execute a Structured Query Language (“SQL”) statement. While the database 102 is carrying out the requested function, the process thread 100 must wait, as illustrated in FIG. 1 step 2. Step 3 shows that upon completion of the requested task, the database 102 can return the results to the process thread 100, and the process thread 100 can finally resume operations.
One problem with a synchronous API 101 such as that of FIG. 1 is the time that a process thread 100 must wait in FIG. 1 step 2. This is time that process thread 100 could productively spend engaging in other tasks. (If the combined processes of FIG. 1 are analogized to a pizza restaurant, the process thread 100 would be an employee, the API 101 would be company procedure, and the database 102 would be the pizza oven. The process thread 100 employee is waiting in front of the database 102 oven for the function call pizza to bake, instead of continuing to grate cheese, chop tomatoes, and mix pizza dough. This is a waste of resources.)
Various partial solutions have been developed in the past to address this waste of resources. These solutions are illustrated in FIG. 2 and FIG. 3. While some of these solutions have been referred to as “nonblocking” database APIs, it will become clear from the following discussion that a nonblocking API is not necessarily the same as an asynchronous API. A nonblocking API may wholly or partially free a given process thread from waiting for a database to return operation results, but requires some dedication of process thread resources to either waiting for or checking on a database while an operation is ongoing. Sample nonblocking scenarios are provided in FIG. 2 and FIG. 3 and the corresponding discussion below. Note that such “nonblocking” APIs are not truly asynchronous in that they do not free all application thread resources from interacting with a database while an operation is carried out by the database.
The solution of FIG. 3 is referred to as “polling.” FIG. 3 shows a reconfigured API 103 that allows a process thread 100 to query a database 102 about whether an operation is completed. Once again, the procedure can be demonstrated in three steps. As in FIG. 1, the first step is a function call from the process thread 100 to the API 103. Instead of simply waiting for results regardless of how long it may take, this configuration allows the process thread 100 continue executing, but it must periodically check whether the database 102 has completed the requested operation. The database 102 is “polled,” or asked whether an operation is completed. This is illustrated in step 2. The immediate return of “yes” or “no” answer from the database 102 allows the process thread 100 to continue executing, however if the answer is “no,” the process thread 100 must again poll the database at a later time to check if the operation is complete. A “yes” answer, on the other hand, may be accompanied by database 102 results, marking the end of a database 102 interaction. The API of FIG. 3 is not full asynchronous, because calling process threads 100 must dedicate resources to repeatedly calling a database 102 until database results are available.
The solution of FIG. 3 is an improvement over the procedure in FIG. 1, but it retains some notable drawbacks. Occasionally a database 102 is bombarded with too many calls, and the responsiveness of the database 102 is impaired. Every time the process thread 100 polls the database 102, it must wait for the “yes” or “no” answer from the database 102. This means waiting for the poll to be packaged and transmitted to a database 102, received, processed and returned by the database 102, and finally received, unpackaged, and delivered to the process thread 100. This is not a truly asynchronous solution because it involved a process thread 100 initiating further communication with a database 102 prior to receiving a signal that an operation is complete. In the pizza restaurant analogy, this is similar to allowing an employee returning to a pizza oven every 10 seconds to check whether a pizza is done, rather than continuing to work and receiving notification by a bell or buzzer that a pizza is done. Again, this is a waste of resources.
FIG. 2 shows another solution that has been partially successful in addressing the problem of blocking process threads when making database calls. Again, three steps are illustrated, and the first step represents a call to a database 102. The second step demonstrates that instead of allowing a thread 100 to wait for results, an application or process can spawn a new thread 104, or waiting thread 104, and allow the waiting thread 104 to wait for the database 102 to return results while the original process thread 100 can continue operations. In FIG. 2 step 3, the database 102 returns results to the waiting thread 104, which can perform the task of communicating the results to the original process thread 100.
Like the solution of FIG. 3, the solution of FIG. 2 is not wholly adequate. Allowing process thread 100 to continue operations despite the time required by a database 102 to return results is an improvement. However, the waiting thread 104 consumes valuable system resources. To analogize once again to a pizza restaurant, the solution of FIG. 3 is akin to hiring a new waiting thread 104 employee for the express purpose of watching the database 102 pizza oven. This allows other employees to go about their work and not watch their pizzas bake, but the waiting thread 104 employee must be paid, thereby consuming system resources, and could be more productively occupied than simply waiting and watching for database 102 pizzas to bake. Again, this is a waste of resources.
In light of the forgoing deficiencies, there is an unaddressed need in the industry to provide an improved database API.